package com.lh.marketing.runner;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public interface MyScheduleTask {

    /**
     * 所有定时任务类都需要重写该方法，执行定时任务的业务代码.
     */
    void run();

    /**
     * 内部注入类，将定时任务和表达式注入到线程池的任务中.
     */
    @Component
    @Slf4j
    class Initializer {

        @Autowired
        private ApplicationContext context;

        @Autowired
        private RedissonClient redisson;

        // 线程池任务调度类，能够开启线程池进行任务调度
        private ThreadPoolTaskScheduler exec = new ThreadPoolTaskScheduler();

        // 将2秒换算成毫秒，minTaskProcessTime=2000毫秒
        // 判断任务的用时时间，如果小于2秒，则睡眠2秒后再结束定时任务
        private static final Long minTaskProcessTime = TimeUnit.SECONDS.toMillis(2);

        @PostConstruct
        public void init() {

            // 初始化线程池
            exec.setPoolSize(10);
            exec.setThreadGroupName("my-task");
            exec.initialize();

            // 获取所有实现PayScheduleTask接口的定时任务
            Map<String, MyScheduleTask> beansOfType = context.getBeansOfType(MyScheduleTask.class);

            beansOfType.values().forEach(task -> {
                // 获取定时任务类的注解@PayScheduled
                MyScheduled myScheduled = task.getClass().getAnnotation(MyScheduled.class);
                //启用事务后会生成代理类,再次查找代理类父类的注解.
                if (myScheduled == null) {
                    myScheduled = task.getClass().getSuperclass().getAnnotation(MyScheduled.class);
                }
                if (myScheduled != null) {
                    // 变成final，用于lambda表达式中
                    final MyScheduled scheduled = myScheduled;

          /*
          schedule()方法会创建一个定时计划ScheduledFuture，在这个方法需要添加两个参数，
          Runnable（线程接口类） 和CronTrigger（定时任务触发器）
          Runnable线程用于执行定时任务，CronTrigger设定定时任务的cron表达式
           */
                    exec.schedule(() -> {
                        RLock lock = redisson.getLock(RedisKeys.SCHEDULE_TASK_LOCK.build(toOriginalName(task.getClass().getSimpleName())));

                        try {
                            // 编程规约：lock.tryLock()方法不要放在try代码块中，因为lock.tryLock()可能会抛出uncheck异常
                            if (lock.tryLock(0L, scheduled.releaseTime(), scheduled.timeUnit())) {
                                try {
                                    long start = System.currentTimeMillis();
                                    // task表示定时任务类，执行定时任务类重写的run()方法
                                    task.run();
                                    long timeCost = System.currentTimeMillis() - start;
                                    log.info("lock success :{} hold count is :{} , task process cost: {}ms", lock.getName(), lock.getHoldCount(), timeCost);
                                    //如果任务耗时不足最小处理时间,则sleep 2秒,降低多实例任务冲突.
                                    if (timeCost < minTaskProcessTime) {
                                        TimeUnit.SECONDS.sleep(2);
                                    }

                                } catch (InterruptedException e) {
                                    log.error("计划任务执行失败", e);
                                } finally {
                                    lock.unlock();
                                    log.debug("release lock :{}", lock.getName());
                                }
                            } else {
                                log.info("lock faild :{} hold count is :{}", lock.getName(), lock.getHoldCount());
                            }
                        } catch (InterruptedException e) {
                            log.error("lock error .", e);
                        }
                    }, new CronTrigger(myScheduled.cron()));
                }
            });
        }

        /**
         * 关闭线程池之前，执行该方法.
         */
        @PreDestroy
        public void desgtroy() {
            exec.shutdown();
        }

        /**
         * 获取类名.
         */
        private static String toOriginalName(String cglibName) {
            return cglibName.contains("$") ? cglibName.substring(0, cglibName.indexOf("$")) : cglibName;
        }
    }

}

